﻿// ***********************************************************************
// Assembly         : Tx.OCC
// Author           : tianteng
// Created          : 03-09-2021
//
// Last Modified By : tianteng
// Last Modified On : 01-26-2021
// ***********************************************************************
// <copyright file="TopoUtils.cs" company="Tobias Schachte">
//     Copyright © 2015-2021 Tobias Schachte
// </copyright>
// <summary></summary>
// ***********************************************************************
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Occt;

namespace Tx.OCC
{
    /// <summary>
    /// Class TopoUtils.
    /// </summary>
    public static class OccTopoEx
    {
        /// <summary>
        /// Makes the edge.
        /// </summary>
        /// <param name="curve2d">The curve2d.</param>
        /// <param name="plane">The plane.</param>
        /// <returns>TopoDS_Edge.</returns>
        public static TopoDS_Edge MakeEdge(Geom2d_Curve curve2d, gp_Pln plane)
        {
            var curve = GeomAPI.To3d(curve2d, plane);
            var edge = new BRepBuilderAPI_MakeEdge(curve).Edge;

            return edge;
        }

        /// <summary>
        /// Makes the face.
        /// </summary>
        /// <param name="points">The points.</param>
        /// <returns>TopoDS_Face.</returns>
        public static TopoDS_Face MakeFace(gp_Pnt[] points)
        {
            Debug.Assert(points.Length == 4);

            var edges = new TopoDS_Edge[4];
            for (int i = 0; i < 4; i++)
            {
                edges[i] = new BRepBuilderAPI_MakeEdge(points[i], points[i == 3 ? 0 : i + 1]).Edge;
            }

            var wire = new BRepBuilderAPI_MakeWire(edges[0], edges[1], edges[2], edges[3]).Wire;
            return new BRepBuilderAPI_MakeFace(wire).Face;
        }

        /// <summary>
        /// Creates the faces from wires.
        /// </summary>
        /// <param name="sourceShape">The source shape.</param>
        /// <param name="plane">The plane.</param>
        /// <returns>TopoDS_Shape.</returns>
        public static TopoDS_Shape CreateFacesFromWires(TopoDS_Shape sourceShape, gp_Pln plane, Action<string> messageWarning, Action<string> messageError)
        {
            var wires = sourceShape.Wires();

            // Create faces from closed wires
            var openWireCount = 0;
            var closedWireCount = 0;
            var makeFace = new BRepBuilderAPI_MakeFace(plane);
            foreach (var wire in wires)
            {
                var checkWire = new BRepCheck_Wire(wire);
                if (checkWire.Closed() != BRepCheck_Status.BRepCheck_NoError)
                {
                    openWireCount++;
                    continue;
                }
                makeFace.Add(wire);
                closedWireCount++;
            }

            if (openWireCount > 0)
            {
                messageWarning?.Invoke("Source shape has " + openWireCount + " unclosed wires, which will be ignored.");
            }

            if (closedWireCount == 0)
            {
                messageWarning?.Invoke("Source shape has no closed wires.");
                //return new TopoDS_Face();
                return null;
            }

            if (!makeFace.IsDone)
            {
                messageError?.Invoke("Generating faces from source wires failed.");
                return null;
            }

            // Fix orientation of that faces
            var shapeFix = new ShapeFix_Shape(makeFace.Face);
            shapeFix.Perform();

            return shapeFix.Shape;
        }

        /// <summary>
        /// Updates the face orientation.
        /// </summary>
        /// <param name="face">The face.</param>
        /// <param name="referenceDirection">The reference direction.</param>
        public static void UpdateFaceOrientation(TopoDS_Face face, gp_Dir referenceDirection)
        {
            if (OccFaceEx.GetFaceCenterNormal(face).Direction.IsOpposite(referenceDirection, MathEx.PI_2))
            {
                face.Reverse();
            }
        }

        /// <summary>
        /// Updates the solid orientation.
        /// </summary>
        /// <param name="solid">The solid.</param>
        public static void UpdateSolidOrientation(TopoDS_Shape solid)
        {
            var classifier = new BRepClass3d_SolidClassifier(solid);
            classifier.PerformInfinitePoint(Precision.Confusion);
            if (classifier.State == TopAbs_State.TopAbs_IN)
            {
                solid.Reverse();
            }
        }

        /// <summary>
        /// Makes the solid.
        /// </summary>
        /// <param name="shell">The shell.</param>
        /// <param name="correctOrientation">if set to <c>true</c> [correct orientation].</param>
        /// <returns>TopoDS_Solid.</returns>
        public static TopoDS_Solid MakeSolid(TopoDS_Shell shell, bool correctOrientation)
        {
            var builder = new BRep_Builder();
            builder.MakeSolid(out TopoDS_Solid solid);
            builder.Add(solid, shell);

            if (correctOrientation)
            {
                var classifier = new BRepClass3d_SolidClassifier(solid);
                classifier.PerformInfinitePoint(Precision.Confusion);
                if (classifier.State == TopAbs_State.TopAbs_IN)
                {
                    builder.MakeSolid(out solid);
                    builder.Add(solid, shell.Reversed);
                }
            }

            return solid;
        }

        /// <summary>
        /// Finds the edges by vertex.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <param name="vertex">The vertex.</param>
        /// <returns>List&lt;TopoDS_Shape&gt;.</returns>
        public static List<TopoDS_Shape> FindEdgesByVertex(TopoDS_Shape shape, TopoDS_Vertex vertex)
        {
            // Get map of all faces with their ancestors
            TopExp.MapShapesAndAncestors(shape, TopAbs_ShapeEnum.TopAbs_VERTEX, TopAbs_ShapeEnum.TopAbs_EDGE, out TopTools_IndexedDataMapOfShapeListOfShape edgeMap);

            return edgeMap.FindFromKey(vertex).ToList();
        }
    }
}